package org.nlp2rdf.navigator.benchmark;

import com.google.common.base.Joiner;
import com.google.common.collect.HashMultimap;
import com.google.common.collect.Multimap;
import com.google.common.collect.Sets;
import com.hp.hpl.jena.query.QuerySolution;
import com.hp.hpl.jena.query.ResultSet;
import org.aksw.commons.semweb.sparql.core.CachingSparqlEndpoint;
import org.aksw.commons.semweb.sparql.core.HttpSparqlEndpoint;
import org.aksw.commons.semweb.sparql.core.ISparqlEndpoint;
import org.aksw.commons.semweb.sparql.core.QueryCollection;
import org.aksw.commons.util.Files;
import org.aksw.commons.util.collections.MultiMaps;

import java.io.File;
import java.io.FileOutputStream;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;


class Stats {
    public double precision;
    public double fMeasure;
    public double recall;
}



class MyColumn
{
    //IData
}

class MyRow {
    
}

class MyTable {

}




/*
class DatatypeTable {
    public <T> DatatypeTable create(Class<T> clazz) {
        return null;
    }
}

interface IObjectFactory
{
    Object create();
}

class NoArgConstructorObjectFactory<T>
    implements IObjectFactory
{
    private Class<?> clazz;

    public NoArgConstructorObjectFactory(Class<?> clazz) {
    }

    public T create() {
        return clazz.newInstance();        
    }
}

class ObjectFactoryRegistry
{
    public
}
*/




/**
 * Created by Claus Stadler
 * Date: Oct 27, 2010
 * Time: 1:59:33 PM
 */
public class TableGenerator {

    public static <K, S, T> Map<S, T> getOrCreate(Map<K, Map<S, T>> map, K key)
    {
        Map<S, T> value = map.get(key);
        if(value == null) {
            value = new HashMap<S, T>();

            map.put(key, value);
        }

        return value;
    }

    public static <K, S> Set<S> getOrCreate(Map<K, Set<S>> map, K key)
    {
        Set<S> value = map.get(key);
        if(value == null) {
            value = new HashSet<S>();

            map.put(key, value);
        }

        return value;
    }




    public static Map<String, Map<Integer, Map<Integer, Stats>>> getDataForExperiment(String strategyName)
        throws Exception
    {
        ISparqlEndpoint coreSparqlEndpoint = new HttpSparqlEndpoint("http://localhost:8890/sparql");
        ISparqlEndpoint sparqlEndpoint = new CachingSparqlEndpoint(coreSparqlEndpoint, "/tmp/sparqlCache");

        String query = Files.readContent(new File("Table.sparql"));

        query = query.replace("$graphName", strategyName);


        Iterable<QuerySolution> col = new QueryCollection(sparqlEndpoint, query).asJava();
        //Map<String, Treemap<Integer, Treemap>>
        Map<String, Map<Integer, Map<Integer, Stats>>> data = new HashMap<String, Map<Integer, Map<Integer, Stats>>>();

        for(QuerySolution qs : col) {
            String category = qs.get("categoryId").toString();
            Integer foldId = (Integer)qs.get("foldId").asNode().getLiteralValue();
            Integer phaseId = (Integer)qs.get("phaseId").asNode().getLiteralValue();

            Stats s = new Stats();
            s.precision = (Double)qs.get("precisionAllTypedValue").asNode().getLiteralValue();
            s.recall = (Double)qs.get("recallAllTypedValue").asNode().getLiteralValue();
            s.fMeasure = (Double)qs.get("fMeasureAllTypedValue").asNode().getLiteralValue();

            //Map<Integer, Stats> x =

            getOrCreate(getOrCreate(data, category), foldId).put(phaseId, s);
        }

        return data;
    }


    public static String[][] generateFMeasureBucketTable(List<String> experimentNames, Set<String> categoryNames, int numBuckets, int phaseId)
            throws Exception
    {
        Map<String, Map<String, Map<Integer, Map<Integer, Stats>>>> datas = new HashMap<String, Map<String, Map<Integer, Map<Integer, Stats>>>>();

        for(String experimentName : experimentNames) {
            Map<String, Map<Integer, Map<Integer, Stats>>> data = getDataForExperiment(experimentName); 

            if(categoryNames != null) {
                data.keySet().retainAll(categoryNames);
            } else {
                Set<String> cleanCategories = getCleanCategories(data);
                data.keySet().retainAll(cleanCategories);
            }

            datas.put(experimentName, data);
        }

        String[][] table = new String[numBuckets + 1][experimentNames.size() + 1];

        // Write header
        for(int y = 0; y < numBuckets; ++y) {
            double rangeSize = 100.0 / (double)numBuckets;
            double rangeStart = rangeSize * y;
            double rangeEnd = rangeStart + rangeSize;

            table[y + 1][0] = (int)rangeStart + "-" + (int)rangeEnd + "%";
        }

        table[0][0] = "Phase" + phaseId;
        for(int x = 0; x < experimentNames.size(); ++x) {
            table[0][x + 1] = experimentNames.get(x);
        }


        for(int x = 0; x < experimentNames.size(); ++x) {
            String experimentName = experimentNames.get(x);
            Map<String, Map<Integer, Map<Integer, Stats>>> data = datas.get(experimentName);

            List<Integer> bucket = fMeasureBagsPerCategory(data, phaseId, numBuckets);
            List<Double> norm = normalize(bucket);
            //System.out.println(norm);


            for(int y = 0; y < bucket.size(); ++y) {
                table[y + 1][x + 1] = norm.get(y).toString();
            }
        }

        return table;
    }

    public static List<Double> normalize(List<Integer> list)
    {
        double sum = 0;
        for(Integer item : list) {
            sum += item;
        }

        List<Double> norm = new ArrayList<Double>();
        for(Integer item : list) {
            norm.add(item / sum);
        }

        return norm;
    }


    public static void main(String[] args)
            throws Exception
    {
        //mainNew(args);
        //mainOld(args);
        getFalsePositivesAndFalseNegatives("Exp2Random", 0.8f);
    }

    public static void mainNew(String[] args)
            throws Exception

    {
        List<String> strategyNames = Arrays.asList("Exp2Random", "Exp2History", "Exp2Search");


        for(int i=0; i<6; ++i) {
            String[][] table = generateFMeasureBucketTable(strategyNames, null, 5, i);


            for(int x = 0; x < table.length; ++x) {
                System.out.println(Joiner.on("\t").join(Arrays.asList(table[x])));
            }
            System.out.println();
        }
    }



    public static void getFalsePositivesAndFalseNegatives(String strategyName, float fMeasureThreshold)
        throws Exception
    {
        ISparqlEndpoint coreSparqlEndpoint = new HttpSparqlEndpoint("http://localhost:8890/sparql");
        ISparqlEndpoint sparqlEndpoint = new CachingSparqlEndpoint(coreSparqlEndpoint, "/tmp/sparqlCache");

        String query = Files.readContent(new File("Table.sparql"));

        query = query.replace("$graphName", strategyName);


        Iterable<QuerySolution> col = new QueryCollection(sparqlEndpoint, query).asJava();

        Pattern setSize = Pattern.compile("^\\((\\d+)\\)");
        

        //Map<String, Treemap<Integer, Treemap>>
        Map<String, Map<Integer, Map<Integer, Stats>>> data = new HashMap<String, Map<Integer, Map<Integer, Stats>>>();

        for(QuerySolution qs : col) {
            String category = qs.get("categoryId").toString();
            Integer foldId = (Integer)qs.get("foldId").asNode().getLiteralValue();
            Integer phaseId = (Integer)qs.get("phaseId").asNode().getLiteralValue();

            Stats s = new Stats();
            s.precision = (Double)qs.get("precisionAllTypedValue").asNode().getLiteralValue();
            s.recall = (Double)qs.get("recallAllTypedValue").asNode().getLiteralValue();
            s.fMeasure = (Double)qs.get("fMeasureAllTypedValue").asNode().getLiteralValue();

            if(s.fMeasure > fMeasureThreshold) {
                continue;
            }

            String falsePositives = (String)qs.getLiteral("falsePositivesValue").getValue();
            String falseNegatives = (String)qs.getLiteral("falseNegativesValue").getValue();


            //System.out.println(falsePositives.toString().replace(" ", "$"));

            Matcher ssMFp = setSize.matcher(falsePositives.toString());
            ssMFp.find();
            int ssFp = Integer.parseInt(ssMFp.group(1));

            Matcher ssMFn = setSize.matcher(falseNegatives.toString());
            ssMFn.find();
            int ssFn = Integer.parseInt(ssMFn.group(1));

            if(true) {
                if(ssFp > 0 && ssFp < 20) {
                    System.out.println(category);
                    System.out.println(falsePositives);
                }
            }

            if(false) {
                if(ssFn > 0 && ssFn < 10) {
                    System.out.println(category);
                    System.out.println(falseNegatives);
                }
            }



            //System.out.println(ssNp);

            //System.out.println("fp: " + falsePositives);
            //System.out.println("fn: " + falseNegatives);

            //Map<Integer, Stats> x =

            //getOrCreate(getOrCreate(data, category), foldId).put(phaseId, s);
        }

        //return data;
    }




    public static void mainOld(String[] args)
            throws Exception

    {
        String strategyName = "MyExp";
        int numBags = 9;

        Map<String, Map<Integer, Map<Integer, Stats>>> data = getDataForExperiment(strategyName);
        /*
        Map<String, Map<Integer, Map<Integer, Stats>>> searchData = getDataForExperiment("Exp2Search");
        Map<String, Map<Integer, Map<Integer, Stats>>> historyData = getDataForExperiment("Exp2History");
        Map<String, Map<Integer, Map<Integer, Stats>>> randomData = getDataForExperiment("Exp2Random");



        Set<String> cleanCategoriesSearch = getCleanCategories(searchData);
        Set<String> cleanCategoriesHistory = getCleanCategories(historyData);
        Set<String> cleanCategoriesRandom = getCleanCategories(randomData);

        System.out.println(Sets.intersection(Sets.intersection(cleanCategoriesSearch, cleanCategoriesHistory), cleanCategoriesRandom).size());

        Set<String> cleanCategories = cleanCategoriesSearch;
        Map<String, Map<Integer, Map<Integer, Stats>>> data = searchData;

        if(true)
        System.exit(1);
        */
        //Set<String> cleanCategories = getCleanCategories(data);


        //data.keySet().retainAll(cleanCategories);

        System.out.println("#Categories = " + data.keySet().size());


        // AvgFMeasureBags for the cleaned categories

        for(int i = 0; i < 5; ++i) {
            File file = new File(strategyName + "CountVsFMeasureForPhase" + (i + 1) + ".data");
            PrintWriter writer = new PrintWriter(new FileOutputStream(file));

            List<Integer> bag = fMeasureBagsPerCategory(data, i, numBags);

            System.out.println("Bucket=" + bag);
            // Normalize
            int sum = 0;
            for(Integer item : bag) {
                sum += item;
            }

            List<Double> norm = new ArrayList<Double>();
            for(Integer item : bag) {
                norm.add(item / (double)sum);
            }



            for(int j = 0; j < norm.size(); ++j) {
                writer.println(j + "\t" + norm.get(j));
            }
            writer.flush();
            writer.close();

            //System.out.println(bag);
        }

    }

    public static double getAvgFMeasure(Map<Integer, Map<Integer, Stats>> data, int phaseId) {
        double sum = 0;
        int denominator = 0;
        for(Map<Integer, Stats> a : data.values()) {
            Stats stats = a.get(phaseId + 1);
            if(stats == null) {
                continue;
            }

            sum += stats.fMeasure;
            ++denominator;
        }

        return sum / (double)denominator;
    }


    public static List<Integer> fMeasureBagsPerCategory(Map<String, Map<Integer, Map<Integer, Stats>>> data, int phaseId, int numBags) {

        int[] bags = new int[numBags];
        Arrays.fill(bags, 0);


        for(Map<Integer, Map<Integer, Stats>> a : data.values()) {

            double avgFMeasure = getAvgFMeasure(a, phaseId);
            int bagId = Math.min((int)(numBags * avgFMeasure), numBags - 1);

            ++bags[bagId];
        }

        List<Integer> result = new ArrayList<Integer>();
        for(int item : bags) {
            result.add(item);
        }
        return result;
    }

    public static List<Integer> fMeasureBagsPerFold(Map<String, Map<Integer, Map<Integer, Stats>>> data, int phaseId, int numBags) {

        int[] bags = new int[numBags];
        Arrays.fill(bags, 0);



        for(Map<Integer, Map<Integer, Stats>> a : data.values()) {
            for(Stats b : a.get(phaseId + 1).values()) {
                int bagId = Math.min((int)(numBags * b.fMeasure), numBags - 1);

                ++bags[bagId];
            }
        }

        List<Integer> result = new ArrayList<Integer>();
        for(int item : bags) {
            result.add(item);
        }
        return result;
    }

    public static Set<String> getCleanCategories(Map<String, Map<Integer, Map<Integer, Stats>>> data)
    {
        Set<String> result = new TreeSet<String>();

        for(Map.Entry<String, Map<Integer, Map<Integer, Stats>>> a : data.entrySet()) {
            if(a.getValue().size() != 5) {
                continue;
            }

            boolean ok = true;
            for(Map.Entry<Integer, Map<Integer, Stats>> b : a.getValue().entrySet()) {
                if(b.getValue().size() != 5) {
                    ok = false;
                    break;
                }

            }

            if(ok) {
                result.add(a.getKey());
            }
        }

        return result;
    }

    public void rocCurve()
    {
        Map<String, Map<Integer, Map<Integer, Stats>>> data = new HashMap<String, Map<Integer, Map<Integer, Stats>>>();

        System.out.println(data.keySet().size());

        Set<String> cleanCategories = new TreeSet<String>();

        for(Map.Entry<String, Map<Integer, Map<Integer, Stats>>> a : data.entrySet()) {
            if(a.getValue().size() != 5) {
                continue;
            }

            boolean ok = true;
            for(Map.Entry<Integer, Map<Integer, Stats>> b : a.getValue().entrySet()) {
                if(b.getValue().size() != 5) {
                    ok = false;
                    break;
                }

            }

            if(ok) {
                cleanCategories.add(a.getKey());                
            }
        }
        System.out.println(cleanCategories.size());
        //For each strategy: For each phase: Determine the avg precision and recall
        double[] sumPrecisions = new double[5];
        double[] sumRecalls = new double[5];

        double[] numPrecisions = new double[5];
        double[] numRecalls = new double[5];


        // true positive rate = precision
        // false positive rate = 1 - true negative rate 

        //int numPrecisions = 0;
        //int numRecalls = 0;
        for(String category : cleanCategories) {

            for(Map.Entry<Integer, Map<Integer, Stats>> fold : data.get(category).entrySet()) {
                for(Map.Entry<Integer, Stats> phase : fold.getValue().entrySet()) {
                    int index = phase.getKey() - 1;

                    sumPrecisions[index] += phase.getValue().precision;
                    sumRecalls[index] += phase.getValue().recall;

                    ++numPrecisions[index];
                    ++numRecalls[index];
                }
            }
        }

        for(int i = 0; i < sumRecalls.length; ++i) {
            double avgPrecision = sumPrecisions[i] / numPrecisions[i];
            double avgRecall = sumRecalls[i] / numRecalls[i];

            System.out.println(avgPrecision + "\t" + avgRecall);
        }


    }




}
